﻿//-----------------------------------------------------------------------
// <copyright file="WebCoolNodesSource.cs" company="AFS">
// Copyright (c) AFS. All rights reserved.
//
//    This file is part of Chatbox.
//
//    Chatbox is free software: you can redistribute it and/or modify
//    it under the terms of the GNU Lesser General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    any later version.
//
//    Chatbox is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//    GNU Lesser General Public License for more details.
//
//    You should have received a copy of the GNU Lesser General Public License
//    along with Chatbox. If not, see http://www.gnu.org/copyleft/lesser.html
// </copyright>
//----------------------------------------------------------------------- 

namespace ChatBox.Engine.Web
{
    using System;
    using System.IO;
    using System.Linq;
    using System.Text;
    using System.Threading;
    using System.Xml.Linq;

    using ChatBox.DomainObjects;
    using ChatBox.DomainObjects.ResultData;
    using ChatBox.DomainObjects.XmlFeedObjects;
    using ChatBox.Interfaces;
    using ChatBox.Utility;

    /// <summary>
    /// Source for cool nodes
    /// </summary>
    public sealed class WebCoolNodesSource : BaseWebDataSource, ICoolNodesSource, IDisposable
    {        
        #region data

        /// <summary>
        /// The content downloader
        /// </summary>
        private WebClientWithTimeout webClient;
        
        #endregion

        /// <summary>
        /// Initializes a new instance of the WebCoolNodesSource class 
        /// </summary>
        /// <param name="settingsRepository">Settings data</param>
        /// <param name="logger">interface to error logging</param>
        public WebCoolNodesSource(IServerSettingsRepository settingsRepository, ILogger logger)
        {
            this.ServerSettings = settingsRepository.ReadSettings();
            this.Logger = logger;
        }

        #region IChatDataSource Members

        /// <summary>
        /// tell listeners that new messages have arrived
        /// </summary>
        public event EventHandler<EventArgs<CoolNodesDataResult>> DataReceived;

        /// <summary>
        /// Gets or sets the Node id to read 
        /// </summary>
        public int NodeId { get; set; }
        
        /// <summary>
        /// Start going
        /// </summary>
        public void Start()
        {
            if (this.NodeId == 0)
            {
                throw new ChatBoxException("No node id to start cool nodes source");
            }

            if (!this.IsStarted)
            {
                this.IsStarted = true;

                this.webClient = new WebClientWithTimeout();

                // make the thread
                this.ReadThread = new Thread(this.ThreadLoop);
                this.ReadThread.IsBackground = true;
                this.ReadThread.Name = "Chatbox cool nodes read thread";

                // The thread must poll for messages at intervals, 
                // but to start it must not sleep at all until interfaces and initial messages are here
                this.SleepBetweenReads = false;
                this.ReadInterval = this.ServerSettings.TickersRefreshInterval;

                // Start the public message reading thread
                this.ReadThread.Start();
            }
        }

        /// <summary>
        /// IDisposable member
        /// </summary>
        public void Dispose()
        {
            this.DisposeInternal();
            GC.SuppressFinalize(this);
        }

        #endregion

        #region protected

        /// <summary>
        /// The thread has called back with an error
        /// </summary>
        /// <param name="ex">the exception</param>
        protected override void ReadThreadOnError(Exception ex)
        {
            if (this.Logger != null)
            {
                this.Logger.LogException("Cool nodes data read error", ex);
            }

            CoolNodesDataResult failResult = new CoolNodesDataResult(false, String.Empty, ex);
            this.SendMessageData(failResult);
        }

        #endregion

        #region private

        /// <summary>
        /// The thread main loop
        /// </summary>
        private void ThreadLoop()
        {
            while (!this.ThreadTerminated)
            {
                string data = this.DownloadData();

                if (!string.IsNullOrEmpty(data))
                {
                    try
                    {
                        TextReader reader = new StringReader(data);
                        XDocument doc = XDocument.Load(reader);
                        this.ReadThreadDataReceived(doc);
                    }
                    catch (Exception ex)
                    {
                        // report the error, carry on 
                        this.HandleXmlParseException(ex);
                    }
                }

                // wait a bit if the thread is still going, has done the initial reads that happne all at once,
                // or if it's getting error responses wait a bit
                if ((!this.ThreadTerminated && this.SleepBetweenReads) || string.IsNullOrEmpty(data))
                {
                    Thread.Sleep(this.ReadInterval);
                }
            }
        }

        /// <summary>
        /// download data from the web server
        /// </summary>
        /// <returns>the data downloaded</returns>
        private string DownloadData()
        {
            try
            {
                this.webClient.Headers.Add("user-agent", EngineConstants.UserAgentString);

                // return this.webClient.DownloadString(this.Url);

                // Cannot do the above- we have to fiddle with the text encoding
                // because e2 lies about the xml that it sends. The headers say
                // "Content-Type: text/xml; charset=ISO-8859-1" (i.e. Latin-1)
                // but it is in fact UTF8
                byte[] data = this.webClient.DownloadData(NodeLink(this.ServerSettings.Server, this.NodeId));
                return Encoding.UTF8.GetString(data);
            }
            catch (Exception ex)
            {
                this.HandleWebException(ex);
                return string.Empty;
            }
        }

        /// <summary>
        /// The thread had called back with data in an XDocument
        /// </summary>
        /// <param name="document">the data received</param>
        private void ReadThreadDataReceived(XDocument document)
        {
            CoolNodesDataResult resultData = document.ToCoolNodes();

            // once we have first messages, pause between polling the server
            this.SleepBetweenReads = true;
            
            int itemsCount = resultData.CoolNodes.Count();
            resultData.Message = itemsCount + " cool nodes received";

            this.SendMessageData(resultData);
        }

        /// <summary>
        /// Notify listeners of message data
        /// </summary>
        /// <param name="messageData">Message data to send</param>
        private void SendMessageData(CoolNodesDataResult messageData)
        {
            this.SetSequence(messageData);

            if (this.DataReceived != null)
            {
                this.DataReceived(this, new EventArgs<CoolNodesDataResult>(messageData));
            }
        }

        /// <summary>
        /// Shut down the message read thread and dispose of resources
        /// </summary>
        private void DisposeInternal()
        {
            if (this.ReadThread != null)
            {
                this.ThreadTerminated = true;
                this.ReadThread = null;
            }

            this.webClient.TestDispose();
            this.webClient = null;
        }

        #endregion
    }
}
